﻿using System;
using System.Collections.Generic;
using System.Text;

namespace JSH.ITDiplom.OOPC01.PersonalIdentityNumber.BusinessLayer.da
{
    /// <summary>
    /// This class is used to validate and generate Danish personal identity numbers.
    /// See http://da.wikipedia.org/wiki/CPR-nummer for a description of the danish
    /// personal identity number.
    /// </summary>
    public sealed class DanishPersonalIdentityNumber : PersonalIdentityNumber
    {
        private string personalIdentityNumberWithDash;
        /// <summary>
        /// Min. birthdate supported by danish personal identity number
        /// </summary>
        public static DateTime MIN_DATE = new DateTime(1858, 1, 1);
        /// <summary>
        /// Max. birthdate supported by danish personal identity number
        /// </summary>
        public static DateTime MAX_DATE = new DateTime(2057, 12, 31);
        
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="personalIdentityNumber">
        /// Danish personal identity number
        /// </param>
        public DanishPersonalIdentityNumber(string personalIdentityNumber)
            : base(personalIdentityNumber)
        {
            SetPersonalIdentityNumberWithDash();
            SetBirthDay();
            SetGender();
            validations = new IPersonalIdentityNumberValidation[3];
            validations[0] = new FormatValidation(personalIdentityNumberWithDash);
            validations[1] = new DateValidation(BirthDay);
            validations[2] = new Modulus11Validation(personalIdentityNumberWithDash,
                BirthDay, Gender);
        }
     
        /// <summary>
        /// Add a dash to the personal number.
        /// </summary>
        private void SetPersonalIdentityNumberWithDash()
        {
            if (personalIdentityNumber.Length == 10)
            {
                personalIdentityNumberWithDash = 
                    personalIdentityNumber.Substring(0, 6)
                    + "-" + personalIdentityNumber.Substring(6); 
            }
            else
            {
                personalIdentityNumberWithDash = personalIdentityNumber; 
            }
        }

        /// <summary>
        /// Calc. full year.
        /// </summary>
        /// <returns></returns>
        private int GetFullYear()
        {
            int digit8 = Int32.Parse(personalIdentityNumberWithDash.Substring(7, 1));
            int fullYear = 0;
            int year = Int32.Parse(personalIdentityNumberWithDash.Substring(4, 2));
            switch (digit8)
            {
                case 0:
                case 1:
                case 2:
                case 3:
                    fullYear = 1900 + year;                       
                    break;
                case 4:
                    if (year < 37)
                    {
                        fullYear = 2000 + year;
                    }
                    else
                    {
                        fullYear = 1900 + year;
                    }
                    break;
                case 5:
                case 6:
                case 7:
                case 8:
                    if (year > 57)
                    {
                        fullYear = 1800 + year;
                    }
                    else
                    {
                        fullYear = 2000 + year;
                    }
                    break;
                case 9:
                    if (year > 36)
                    {
                        fullYear = 1900 + year;
                    }
                    else
                    {
                        fullYear = 2000 + year;
                    }
                    break;
            }
            return fullYear;
        }

        /// <summary>
        /// Calc. birtdate and set property.
        /// </summary>
        private void SetBirthDay()
        {
            try
            {
                int fullYear = GetFullYear();
                DateTime date = new DateTime(fullYear, 
                    Int32.Parse(personalIdentityNumber.Substring(2, 2)),
                    Int32.Parse(personalIdentityNumber.Substring(0, 2)));
                BirthDay = date;
            }
            catch
            {
                BirthDay = null;
            }
        }

        /// <summary>
        /// Calc. gender and set property.
        /// </summary>
        private void SetGender()
        {
            try
            {
                int digit11 =
                    Int32.Parse(personalIdentityNumberWithDash.Substring(10, 1));
                int remainder = digit11 % 2;
                if (remainder == 0)
                {
                    Gender = Gender.Female;
                }
                else
                {
                    Gender = Gender.Male;
                }
            }
            catch
            {
                Gender = Gender.Unknown;
            }
        }

        /// <summary>
        /// Generate personal identity numbers.
        /// </summary>
        /// <param name="birthday">Birthday</param>
        /// <param name="gender">Gender (Male or Female)</param>
        /// <returns>A list of valid personal identity numbers</returns>
        public static IList<string> Generate(DateTime birthday, Gender gender)
        {
            if (birthday < MIN_DATE || birthday > MAX_DATE)
            {
                throw new ArgumentException("Birthdate out of date range");
            }

            IList<string> personalNumbers = new List<string>();
            StringBuilder sb = new StringBuilder();
            int day = birthday.Day;
            if (day < 10) sb.Append("0");
            sb.Append(day);
            int month = birthday.Month;
            if (month < 10) sb.Append("0");
            sb.Append(month);
            int year = birthday.Year;
            int lowYearPart = year%100;
            int highYearPart = year - lowYearPart;
            if (lowYearPart < 10) sb.Append("0");
            sb.Append(lowYearPart);
            sb.Append("-");
            string fixedPart = sb.ToString();
            IList<int> valid8Digits = GetValid8Digits(highYearPart, lowYearPart);
            foreach (int i in valid8Digits)
            {
                for (int l = 0; l <= 99; l++)
                {
                    StringBuilder personalNumber = new StringBuilder(fixedPart);
                    personalNumber.Append(i);
                    if (l < 10) personalNumber.Append("0");
                    personalNumber.Append(l);
                    int modulus11 = 
                        Modulus11Validation.Calculate(personalNumber.ToString());
                    if (modulus11 < 10) //Ignore modulus11 if it is 10
                    {
                        if (modulus11%2 == 0 && gender == Gender.Female)
                        {
                            personalNumber.Append(modulus11);
                            personalNumbers.Add(personalNumber.ToString());
                        }
                        else if (modulus11%2 != 0 && gender == Gender.Male)
                        {
                            personalNumber.Append(modulus11);
                            personalNumbers.Add(personalNumber.ToString());
                        }
                    }
                }
            }
            return personalNumbers;
        }

        /// <summary>
        /// Return a list valid 8 digits, for a given year.
        /// </summary>
        /// <param name="highYearPart">Century</param>
        /// <param name="lowYearPart">Year from personal number</param>
        /// <returns></returns>
        private static IList<int> GetValid8Digits(int highYearPart,
            int lowYearPart)
        {
            IList<int> valid8Digits = new List<int>();
            if (highYearPart == 1900)
            {
                valid8Digits.Add(0);
                valid8Digits.Add(1);
                valid8Digits.Add(2);
                valid8Digits.Add(3);
                if (lowYearPart >= 37)
                {
                    valid8Digits.Add(4);
                    valid8Digits.Add(9);
                }
            }
            else if (highYearPart == 2000)
            {
                if (lowYearPart < 37)
                {
                    valid8Digits.Add(4);
                }
                if (lowYearPart <= 57)
                {
                    valid8Digits.Add(5);
                    valid8Digits.Add(6);
                    valid8Digits.Add(7);
                    valid8Digits.Add(8);
                }
                if (lowYearPart <= 36) valid8Digits.Add(9);
            }
            else if (highYearPart == 1800)
            {
                if (lowYearPart > 57)
                {
                    valid8Digits.Add(5);
                    valid8Digits.Add(6);
                    valid8Digits.Add(7);
                    valid8Digits.Add(8);
                }
            }
            return valid8Digits;
        }
    }
}